import { defaults, groupBy } from "lodash";
import {
  IParseResult,
  ICompletionItem,
  ICursorInfo,
  ITableInfo,
  IParserType,
  IDispose,
  ITableParams,
} from "./types";

import { DefaultOpts } from "./defaultOpts";
import { generateUUID } from "./tools";
import * as reader from "./reader";

import { workerCode } from "./worker";

export * from "./types";

export enum SUGGEST_VERSION {
  V1 = 1,
  V2,
}

const asyncParser = async (
  text: string,
  index: number,
  parserType: IParserType,
  parserIndexKey: string
) => {
  window["monacoSqlIntellisense"][parserIndexKey]++;
  const currentParserIndex = window["monacoSqlIntellisense"][parserIndexKey];
  return new Promise((resolve, reject) => {
    window["monacoSqlIntellisenseWorker"][parserIndexKey].postMessage({
      text,
      index,
      parserType,
    });

    window["monacoSqlIntellisenseWorker"][parserIndexKey].onmessage = (
      event
    ) => {
      if (
        currentParserIndex === window["monacoSqlIntellisense"][parserIndexKey]
      ) {
        resolve(event.data);
      } else {
        reject();
      }
    };
  });
};

function returnCompletionItemsByVersion(
  value: ICompletionItem[],
  version: SUGGEST_VERSION
) {
  switch (version) {
    case SUGGEST_VERSION.V1:
      return value;
    case SUGGEST_VERSION.V2:
      return {
        suggestions: value,
      };
    default:
      throw Error("Not supported version");
  }
}

function getSeverityByVersion(monaco: any) {
  return monaco.MarkerSeverity.Error;
}

export default function monacoSqlAutocomplete(
  monaco: any,
  editor: any,
  opts?: Partial<DefaultOpts>
): IDispose {
  const parserIndexKey = generateUUID();
  window["monacoSqlIntellisense"] = {
    ...(window["monacoSqlIntellisense"] || {}),
    [parserIndexKey]: 0,
  };

  // 实例化一个 worker

  window["monacoSqlIntellisenseWorker"] = {
    ...(window["monacoSqlIntellisenseWorker"] || {}),
    [parserIndexKey]: new Worker(URL.createObjectURL(new Blob([workerCode]))),
  };
  opts = defaults(opts || {}, new DefaultOpts(monaco));

  // Get parser info and show error.
  let currentParserPromise: any = null;
  let editVersion = 0;

  editor.onDidChangeModelContent((_: any) => {
    editVersion++;
    const currentEditVersion = editVersion;

    currentParserPromise = new Promise((resolve) => {
      setTimeout(() => {
        const model = editor.getModel();
        asyncParser(
          editor.getValue(),
          model.getOffsetAt(editor.getPosition()),
          opts?.parserType as IParserType,
          parserIndexKey
        ).then((parseResult: any) => {
          resolve(parseResult);

          if (currentEditVersion !== editVersion) {
            return;
          }
          opts?.onParse && opts?.onParse(parseResult);
          if (parseResult.error) {
            const newReason =
              parseResult.error.reason === "incomplete"
                ? `Incomplete, expect next input: \n${parseResult.error.suggestions
                    .map((each: any) => {
                      return each.value;
                    })
                    .join("\n")}`
                : `Wrong input, expect: \n${parseResult.error.suggestions
                    .map((each: any) => {
                      return each.value;
                    })
                    .join("\n")}`;

            const errorPosition = parseResult.error.token
              ? {
                  startLineNumber: model.getPositionAt(
                    parseResult.error.token.position[0]
                  ).lineNumber,
                  startColumn: model.getPositionAt(
                    parseResult.error.token.position[0]
                  ).column,
                  endLineNumber: model.getPositionAt(
                    parseResult.error.token.position[1]
                  ).lineNumber,
                  endColumn:
                    model.getPositionAt(parseResult.error.token.position[1])
                      .column + 1,
                }
              : {
                  startLineNumber: 0,
                  startColumn: 0,
                  endLineNumber: 0,
                  endColumn: 0,
                };

            model.getPositionAt(parseResult.error.token);

            monaco.editor.setModelMarkers(model, opts?.language, [
              {
                ...errorPosition,
                message: newReason,
                severity: getSeverityByVersion(monaco),
              },
            ]);
          } else {
            monaco.editor.setModelMarkers(
              editor.getModel(),
              opts?.language,
              []
            );
          }
        });
      });
    }).catch(console.log);
  });

  monaco.languages.registerCompletionItemProvider(opts.language, {
    triggerCharacters:
      " $.:{}=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".split(
        ""
      ),
    provideCompletionItems: async (_, __, { triggerCharacter }) => {
      try {
        const currentEditVersion = editVersion;
        const parseResult: IParseResult = await currentParserPromise;
        if (currentEditVersion !== editVersion) {
          return returnCompletionItemsByVersion([], opts.version);
        }
        if (!parseResult)
          return returnCompletionItemsByVersion([], opts.version);
        const cursorInfo = await reader.getCursorInfo(
          parseResult.ast,
          parseResult.cursorKeyPath
        );
        let parserSuggestion: ICompletionItem[] = opts?.pipeKeywords
          ? opts.pipeKeywords(parseResult.nextMatchings) || []
          : [];
        if (triggerCharacter === "$") {
          const timeFunList = opts?.onSuggestTimeFunction
            ? await opts.onSuggestTimeFunction()
            : [];
          parserSuggestion = parserSuggestion.concat(timeFunList);
          return returnCompletionItemsByVersion(parserSuggestion, opts.version);
        }
        if (!cursorInfo) {
          return returnCompletionItemsByVersion(parserSuggestion, opts.version);
        }
        let suggestions = [];
        switch (cursorInfo.type) {
          case "env":
            suggestions = parserSuggestion.concat(
              opts.onSuggestEnvs ? await opts.onSuggestEnvs() : []
            );
            return returnCompletionItemsByVersion(suggestions, opts.version);
          case "charset":
            suggestions = parserSuggestion.concat(
              opts.onSuggestCharsets ? await opts.onSuggestCharsets() : []
            );
            return returnCompletionItemsByVersion(suggestions, opts.version);
          case "tableField":
            const cursorRootStatementFields =
              await reader.getFieldsFromStatement(
                parseResult.ast,
                parseResult.cursorKeyPath,
                opts?.onSuggestTableFields as any
              );
            // group.fieldName
            const groups = groupBy(
              cursorRootStatementFields.filter((cursorRootStatementField) => {
                return (
                  cursorRootStatementField?.groupPickerName !== null &&
                  cursorRootStatementField?.groupPickerName !== undefined
                );
              }),
              "groupPickerName"
            );
            const functionNames = opts?.onSuggestFunctionName
              ? await opts.onSuggestFunctionName(cursorInfo.token.value)
              : [];
            suggestions = cursorRootStatementFields
              .concat(parserSuggestion)
              .concat(functionNames)
              .concat(
                groups
                  ? (Object.keys(groups).map((groupName) => {
                      return opts?.onSuggestFieldGroup
                        ? opts.onSuggestFieldGroup(groupName)
                        : groupName;
                    }) as ICompletionItem[])
                  : []
              ) as ICompletionItem[];
            return returnCompletionItemsByVersion(suggestions, opts.version);
          case "tableFieldAfterGroup":
            // 字段 . 后面的部分
            const cursorRootStatementFieldsAfter =
              await reader.getFieldsFromStatement(
                parseResult.ast,
                parseResult.cursorKeyPath as any,
                opts?.onSuggestTableFields as any
              );

            return returnCompletionItemsByVersion(
              cursorRootStatementFieldsAfter
                .filter((cursorRootStatementField: any) => {
                  return (
                    cursorRootStatementField.groupPickerName ===
                    (cursorInfo as ICursorInfo<{ groupName: string }>).groupName
                  );
                })
                .concat(parserSuggestion) as ICompletionItem[],
              opts.version
            );
          case "tableName":
            const tableNames = opts?.onSuggestTableNames
              ? (await opts.onSuggestTableNames(
                  cursorInfo as ICursorInfo<ITableParams>
                )) || []
              : [];
            return returnCompletionItemsByVersion(
              tableNames.concat(parserSuggestion),
              opts.version
            );
          case "functionName":
            return returnCompletionItemsByVersion(
              opts?.onSuggestFunctionName
                ? (await opts.onSuggestFunctionName(cursorInfo.token.value)) ||
                    []
                : [],
              opts.version
            );
          case "udfName":
            return returnCompletionItemsByVersion(
              opts?.onSuggestUdfName
                ? (await opts.onSuggestUdfName(
                    cursorInfo as ICursorInfo<ITableInfo>
                  )) || []
                : [],
              opts.version
            );
          default:
            return returnCompletionItemsByVersion(
              parserSuggestion,
              opts.version
            );
        }
      } catch (e) {
        if (process.env.NODE_ENV !== "development") console.error(e);
        return returnCompletionItemsByVersion([], opts.version);
      }
    },
  });

  // monaco.languages.registerHoverProvider(opts.language, {
  //   provideHover: async (model: any, position: any) => {
  //     const parseResult: IParseResult = await asyncParser(
  //       editor.getValue(),
  //       model.getOffsetAt(position),
  //       opts.parserType
  //     );

  //     const cursorInfo = await reader.getCursorInfo(
  //       parseResult.ast,
  //       parseResult.cursorKeyPath
  //     );

  //     if (!cursorInfo) {
  //       return null as any;
  //     }

  //     let contents: any = [];

  //     switch (cursorInfo.type) {
  //       case "tableField":
  //         const extra = await reader.findFieldExtraInfo(
  //           parseResult.ast,
  //           cursorInfo,
  //           opts.onSuggestTableFields,
  //           parseResult.cursorKeyPath
  //         );
  //         contents = await opts.onHoverTableField(
  //           cursorInfo.token.value,
  //           extra
  //         );
  //         break;
  //       case "tableFieldAfterGroup":
  //         const extraAfter = await reader.findFieldExtraInfo(
  //           parseResult.ast,
  //           cursorInfo,
  //           opts.onSuggestTableFields,
  //           parseResult.cursorKeyPath
  //         );
  //         contents = await opts.onHoverTableField(
  //           cursorInfo.token.value,
  //           extraAfter
  //         );
  //         break;
  //       case "tableName":
  //         contents = await opts.onHoverTableName(cursorInfo as ICursorInfo);
  //         break;
  //       case "functionName":
  //         contents = await opts.onHoverFunctionName(cursorInfo.token.value);
  //         break;
  //       default:
  //     }

  //     return {
  //       range: monaco.Range.fromPositions(
  //         model.getPositionAt(cursorInfo.token.position[0]),
  //         model.getPositionAt(cursorInfo.token.position[1] + 1)
  //       ),
  //       contents,
  //     };
  //   },
  // });

  return function dispose() {
    const worker = window["monacoSqlIntellisenseWorker"][
      parserIndexKey
    ] as Worker;
    worker.terminate();
    delete window["monacoSqlIntellisense"][parserIndexKey];
    delete window["monacoSqlIntellisenseWorker"][parserIndexKey];
  };
}
